package org.hackystat.ui.orb.server;

import java.net.URL;
import java.util.ArrayList;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.xml.datatype.XMLGregorianCalendar;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.ui.orb.client.common.AmbientOrb;
import org.hackystat.ui.orb.client.common.RPCRemoteService;
import org.hackystat.ui.orb.client.common.RPCException;
import org.hackystat.utilities.tstamp.Tstamp;
import org.restlet.Client;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Request;
import org.restlet.data.Response;

/**
 * Provides remote asynchronous service for communicating with the sensorbase and dailyprojectdata 
 * analysis service hosts.
 * @author David J. Nickles
 */
public class RPCServiceImpl extends RemoteServiceServlet implements RPCRemoteService {

  /* Class variables for serialized object id */
  private static final long serialVersionUID = 11121936L;
  /** The sensor properties instance. */
  private org.hackystat.ui.orb.server.Properties properties; 
  /** {@literal} USERLABEL a recurring string constant. */  
  private static final String USERLABEL = ", User: ";

  /**
   * This method sets a specific color and animation on the Orb.
   * 
   * @param orb the state of the Ambient Orb.
   * @return the status of the response.
   */
  public String putOrb(AmbientOrb orb) {
    // Prepare the request
    Request request = new Request(Method.POST, orb.getUrl());
    request.setReferrerRef("http://www.hackystat.org");

    // Handle it using an HTTP client connector
    Client client = new Client(Protocol.HTTP);
    Response response = client.handle(request);
    
    if (response.getStatus().isSuccess()) {
      return Boolean.TRUE.toString();
    }
    else {
      return response.getStatus().getDescription();
    }
  }
  
  /**
   * Returns true if the passed host is a DailyProjectData Analysis service host.
   * 
   * @param host the URI for the DailyProjectData Host.
   * @return Boolean indicating if the host is a valid host.
   */
  public Boolean isDailyProjectDataHost(String host) {
    log("PV checking isDailyProjectDataHost " + host + '\n');
    return DailyProjectDataClient.isHost(host);
  }
  
  /**
   * Returns true if the passed host is a Sensorbase service host.
   * 
   * @param host the URI for the SensorBase Host.
   * @return Boolean indicating if the host is a valid host.
   */
  public Boolean isSensorBaseHost(String host) {
    log("PV checking isSensorBaseHost " + host + '\n');
    return SensorBaseClient.isHost(host);
  }

  /**
   * Returns true if the user and password is registered as a user with the sensorbase host.
   * 
   * @param host the URI for the SensorBase
   * @param user user to authentication
   * @param password user password for authentication
   * @return Boolean indicating if the user and password are valid in the sensorbase
   */
  public Boolean isRegistered(String host, String user, String password) {
    log("PV checking isRegistered with sensorbase, user: " + user + '\n');
    return SensorBaseClient.isRegistered(host, user, password);
  }

  /**
   * Registers the user with this host.
   * 
   * @param host the URI for the SensorBase
   * @param user user to authentication
   * @return Boolean indicating if the user and password are valid in the sensorbase
   */
  public Boolean registerUser(String host, String user) {
    log("PV register, user: " + user + '\n');
    Boolean result = Boolean.TRUE;
    try {
      SensorBaseClient.registerUser(host, user);
    }
    catch (SensorBaseClientException e) {
      result = Boolean.FALSE;
      log("PV register: " + e.getMessage());
    }
    return result;
  }
  
  /**
   * Returns the attributes set in the Manifest file.
   * 
   * @return a list of strings of attribute values from the Manifest file.
   */
  public ArrayList<String> getManifest() {
    ArrayList<String> result = new ArrayList<String>();
    try {
      URL manifestURL = getServletContext().getResource("/META-INF/MANIFEST.MF");
      if (manifestURL == null) {
        log("Unable to get Manifest file.\n");
        return result;
      }
      Manifest mf = new Manifest(manifestURL.openStream());
      Attributes attr = mf.getMainAttributes();
      String title = attr.getValue("Implementation-Title");
      if (title == null) {
        log("Unable to get 'Implementation-Title' attribute from the Manifest.\n");
        title = "Hackystat Developer Widget";
      }
      result.add(title);
      String version = attr.getValue("Implementation-Version");
      if (version == null) {
        log("Unable to get 'Implementation-Version' attribute from the Manifest.\n");
        version = "Development";
      }
      result.add(version);
      String vendor = attr.getValue("Implementation-Vendor");
      if (vendor == null) {
        log("Unable to get 'Implementation-Version' attribute from the Manifest.\n");
        vendor = "CSDL @ UH";
      }
      result.add(vendor);
    } 
    catch (Exception e) {
      log("PV version excpetion thrown " + e.getMessage() + '\n');
    }
    log("PV checking manifest, version=" + result + '\n');
    return result; 
  }
  
  /**
   * This method retrieves the host uris for the Sensorbase and DailyProjectData services from the
   * Project Viewer properties file.
   * 
   * @return An arraylist of the host uris for the Sensorbase and DailyProjectData services from
   * the Project Viewer properties file.
   */
  public ArrayList<String> getHosts() {
    log("Entering method: PV props\n");
    ArrayList<String> result = new ArrayList<String>();
    try {
      this.properties = new org.hackystat.ui.orb.server.Properties();
      if (this.properties == null) {
        log("PV props: Unable to get PV properties file.\n");
        return result;
      }
      result.add(this.properties.getDailyProjectDataHost());
      result.add(this.properties.getSensorbaseHost());
      if (result == null) {
        log("PV props: Unable to get hosts attributes from the properties file.\n");
        return result;
      }
    } 
    catch (Exception e) {
      log("PV props exception thrown " + e.getMessage() + '\n');
    }
    log("PV props success: DailyProjectData host=" + result.get(0) + '\n');
    log("PV props success: Sensorbase host=" + result.get(1) + '\n');
    return result; 
  }
  
  /**
   * Zeros the hour.minute.second.millisecond for this timestamp.
   * @param cal timestamp as XMLGregorianCalendar object
   * @return the quantized timestamp as an XMLGregorianCalendar object
   */
  private XMLGregorianCalendar quantize(XMLGregorianCalendar cal) {
    cal.setHour(0);
    cal.setMinute(0);
    cal.setSecond(0);
    cal.setMillisecond(0);
    return cal;
  }
  
  /**
   * Computes the first day of the given month.
   * Zeros the hour.minute.second.millisecond for this timestamp.
   * @param year the number of the year as an integer
   * @param month the number of the month as an integer
   * @return the first of the month timestamp as an XMLGregorianCalendar object
   */
  private XMLGregorianCalendar getFirstOfMonth(int year, int month) {
    XMLGregorianCalendar cal = Tstamp.makeTimestamp();
    cal.setYear(year);
    cal.setMonth(month);
    cal.setDay(1);
    cal = quantize(cal);
    return cal;
  }
  
  /**
   * Computes the first day of the next month.
   * Zeros the hour.minute.second.millisecond for this timestamp.
   * @param year the number of the year as an integer
   * @param month the number of the month as an integer
   * @return the first of the month timestamp as an XMLGregorianCalendar object
   */
  private XMLGregorianCalendar getFirstOfNextMonth(int year, int month) {
    XMLGregorianCalendar cal = Tstamp.makeTimestamp();
    int nextMonth = ((month % 12) + 1); 
    int nextYear = year;
    if (nextMonth == 1) {
      nextYear++;
    }
    cal.setYear(nextYear);
    cal.setMonth(nextMonth);
    cal.setDay(1);
    cal = quantize(cal);
    return cal;
  }

  /**
   * Return a formatted description of the requested sensor data on the specified interval.
   * @param uri the URI for the SensorBase
   * @param project the project of interest
   * @param user user to authentication
   * @param password user password for authentication
   * @param year of interval
   * @param month of interval
   * @return Boolean array of days with data
   * @throws RPCException if problems occur during the Sensorbase procedure call
   */
  public ArrayList<Boolean> getOverview(String uri, String project, String user, String password, 
    int year, int month) throws RPCException {
    XMLGregorianCalendar start = getFirstOfMonth(year, month);
    XMLGregorianCalendar end = getFirstOfNextMonth(year, month);
    String params = "URI: " + uri + ", Project: " + project + USERLABEL + user 
      + ", Password: " + password + ", year: " +  year + ", month: " + month 
      + ", start: " +  start + ", end: " +  end + '\n';
    log("PV getOverview entering method with params:\n" + params);
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.start();  
    ArrayList<Boolean> result = new ArrayList<Boolean>();
    for (int i = 0; i < 32; i++) {
      result.add(i, Boolean.FALSE);
    }
    try {
      SensorBaseClient client = new SensorBaseClient(uri, user, password);
      SensorDataIndex index = client.getProjectSensorData(user, project, start, end);
      log("PV getOverview: SensorDataIndex retrieved.\n");
      ArrayList<XMLGregorianCalendar> dateslist = new ArrayList<XMLGregorianCalendar>();
      for (SensorDataRef ref : index.getSensorDataRef()) {
        XMLGregorianCalendar cal = quantize(ref.getTimestamp());
        if (!dateslist.contains(cal)) {
          dateslist.add(cal);
        }
      }
      for (XMLGregorianCalendar i : dateslist) {
        result.set(i.getDay(), Boolean.TRUE);
      }
      log("PV getOverview: ArrayList populated with data values.\n");
    } 
    catch (Exception e) {
      log("PV getOverview exception thrown " + e.getMessage() + '\n');
    }
    stopwatch.stop();
    log("PV getOverview exiting method with execution time of " + stopwatch + '\n');
    return result; 
  }

  /**
   * Logs the error message from the javascript client.
   * 
   * @param text message to log.
   */
  public void logClient(String text) {
    log("PV message from client: " + text + '\n');
  }
}